#ifndef USER_INFO_H
#define USER_INFO_H


typedef unsigned int user_server_id;

typedef boost::shared_ptr<class user_info> user_info_ptr;

//int udp_packet_calculate_md5(char *packet_buffer, unsigned int packet_size, char *secret, unsigned int secret_length, unsigned char *md5_out);

class user_info : public boost::enable_shared_from_this<user_info>
{
    typedef enum status
    {
        USER_AUTHENTICATING,
        USER_ACCOUNTING,
        USER_EXIT,
        USER_KICKOFF,
        USER_KICKOFF_FIN
    } STATUS;
    typedef boost::mutex::scoped_lock lock;
public:
    /*user_info(boost::asio::io_service& io_service)
    	: id_(0), output_deadline_(io_service)
    {	}*/
    user_info(int type, const char *name, const char *pass, int version,
              boost::asio::ip::address remote,
              boost::asio::io_service& io_service)
        : login_type_(type),name_(name), password_(pass), version_(version),
          ip_(remote),
		  instant_up_rate_(0), instant_down_rate_(0), total_up_traffic_(0), total_down_traffic_(0),
          packet_id_(0), reply_length_(0), status_(USER_ACCOUNTING),
          login_time_(time(NULL)), update_time_(time(NULL)),
          limit_up_rate_(0), limit_down_rate_(0),	user_type_(-1),
          output_deadline_(io_service)
    {
        id_ = alloc_id();

        unsigned int ld = 0;
        ld = id_;
        sprintf((char *)session_tx_, "%8.8x", ld);
        ld = (unsigned int)time(NULL);
        sprintf((char *)secret_, "%8.8x", ld);    // keep current time as a secret between sever and client
        memcpy(session_tx_ + 8, secret_, 8);


        cout << "user "<< id_ << " enter" << endl;
    }
    ~user_info()
    {
        cout << "user "<< id_ << " leave" << endl;
        free_id(id_);
    }
    user_server_id get_id()
    {
        return id_;
    }
    string &get_name()
    {
        return name_;
    }
    void send_radius_auth(rad_callback_t call_back = NULL)
    {
        rad_packet_ptr p = rad_packet_ptr(new rad_packet(rad_packet::PACKET_AUTH, call_back));
        p->set_auth(name_.c_str(), password_.c_str());
        rad_server::get_best_server()->send_packet(p);
    }
    void send_radius_account(rad_packet::e_account_type type, rad_callback_t call_back = NULL)
    {
        rad_packet_ptr p = rad_packet_ptr(new rad_packet(rad_packet::PACKET_ACCOUNT, call_back));
        p->set_account(type, name_.c_str(), ip_.to_string().c_str(), session_tx_);
        switch (type)
        {
        case rad_packet::ACCT_START:
            p->set_version(boost::lexical_cast<string>(version_));
            break;
        case rad_packet::ACCT_STOP:
            if (status_ == USER_EXIT) p->set_terminate("User-Request");
            if (status_ == USER_ACCOUNTING) p->set_terminate("Session-Timeout");
            if (status_ == USER_KICKOFF) p->set_terminate("Admin-Reset");
            p->set_traffic(total_down_traffic_, total_up_traffic_);
            break;
        case rad_packet::ACCT_ALIVE:
            p->set_traffic(total_down_traffic_, total_up_traffic_);
            break;
        }
        rad_server::get_best_server()->send_packet(p);
    }
    int parse_auth_reply(rad_packet_ptr &p)
    {
        switch ( p->rad_reply_code_ )
        {
        case rad_packet::AUTH_SUCCESS:
            if (p->reply_contain("Veno-Client-Down"))
                limit_down_rate_ = boost::lexical_cast<unsigned int>(p->reply_value("Veno-Client-Down"));
            if (p->reply_contain("Veno-Client-Up"))
                limit_up_rate_ = boost::lexical_cast<unsigned int>(p->reply_value("Veno-Client-Up"));
            if (p->reply_contain("Veno-User-Type"))
                user_type_ = boost::lexical_cast<unsigned int>(p->reply_value("Veno-User-Type"));
            if (p->reply_contain("Veno-Reply-Message"))
                reply_message_ = p->reply_value("Veno-Reply-Message");
            reply_code_ = SERVER_AUTH_REPLY;
            break;
        case rad_packet::AUTH_TIMEOUT:
            reply_message_ = RAD_AUTH_TIMEOUT;
            reply_code_ = SERVER_AUTH_REJECT;
            break;
            // auth reject
        default:
            if (p->reply_contain("Reply-Message"))
                reply_message_ = p->reply_value("Reply-Message");
            reply_code_ = SERVER_AUTH_REJECT;
            break;
        }
        return reply_code_;
    }
    int parse_account_reply(rad_packet_ptr &p)
    {
        switch ( p->rad_reply_code_ )
        {
        case rad_packet::ACCOUNT_SUCCESS:
            break;
        case rad_packet::ACCOUNT_TIMEOUT:
            reply_message_ = RAD_ACCOUNT_TIMEOUT;
            reply_code_ = SERVER_AUTH_REJECT;
            break;
        default:
            reply_message_ = RAD_ACCOUNT_ERROR;
            reply_code_ = SERVER_AUTH_REJECT;
            break;
        }
        return reply_code_;
    }
    int generate_user_reply(boost::asio::streambuf &buffer_)
    {
        int sendlen;
        struct login_reply_server *reply;

        sendlen = AES_pad_align_size(sizeof(login_reply_server) + reply_message_.size());
        unsigned char* out = boost::asio::buffer_cast<unsigned char*>(buffer_.prepare(sendlen));
        reply = (struct login_reply_server *)out;

        switch (reply_code_)
        {
        case SERVER_AUTH_REPLY:
            reply->down_rate = limit_down_rate_;
            reply->up_rate = limit_up_rate_;
            memcpy(reply->secret, secret_, UDP_PACKET_SECRET_KEY_LENGTH);
            reply->table_id = id_;
            reply->user_type = user_type_;
            break;
        default:
            break;
        }
        reply->type = reply_code_;
        reply->msg_length = reply_message_.size();
        memcpy((char *)reply + sizeof(struct login_reply_server), reply_message_.data(), reply_message_.size());
        buffer_.commit(sendlen);
        return 1;
    }
    // class this will not be deleted after this call
    void start_account()
    {
        config_file *cf = config_file::instance;
        {
            lock lk(monitor);
            if (cf->value_yes("disallow_same_username"))
            {
				kick_user_by_name(name_, KEEPALIVE_KICK_OFF);
            }
            online_user_[id_] = shared_from_this();
        }
        output_deadline_.expires_from_now(boost::posix_time::seconds(USER_TIMEOUT_CHECK));
        // authentication time out
        output_deadline_.async_wait(
            boost::bind(&user_info::check_deadline,
                        shared_from_this()));
    }
    void update()
    {
        packet_id_++;
        update_time_ = time(NULL);
        output_deadline_.expires_from_now(boost::posix_time::seconds(USER_TIMEOUT_CHECK));
    }
	static int set_rate_by_name(string &name, unsigned int up, unsigned int down)
	{
		int n = 0;
        for (map<user_server_id, user_info_ptr>::iterator it = online_user_.begin(); it != online_user_.end(); it++)
        {
            if (it->second->get_name() == name && it->second->status_ == USER_ACCOUNTING)
			{
                it->second->set_rate(up, down);
				n++;
			}
        }
        return n;
	}
	void set_rate(unsigned int up, unsigned int down)
	{
		limit_up_rate_ = up;
		limit_down_rate_ = down;
	}
	static int send_message_by_name(string &name, string &msg)
	{
		int n = 0;
        for (map<user_server_id, user_info_ptr>::iterator it = online_user_.begin(); it != online_user_.end(); it++)
        {
            if (it->second->get_name() == name && it->second->status_ == USER_ACCOUNTING)
			{
                it->second->send_message(msg);
				n++;
			}
        }
        return n;
	}
	void send_message(string &msg)
	{
        if (status_ == USER_ACCOUNTING)
        {
			send_message_ += '\0';
            send_message_ += msg;
        }
	}
	static int kick_user_by_name(string &name, string msg = "")
	{
		int n = 0;
        for (map<user_server_id, user_info_ptr>::iterator it = online_user_.begin(); it != online_user_.end(); it++)
        {
            if (it->second->get_name() == name && it->second->status_ == USER_ACCOUNTING)
			{
				it->second->send_message(msg);
				it->second->kick_off();
				n++;
			}
        }
        return n;
	}
    void kick_off()
    {
        if (status_ == USER_ACCOUNTING)
        {
            status_ = USER_KICKOFF;
            send_radius_account(rad_packet::ACCT_STOP);
        }
    }
    unsigned int keep_alive_process(const unsigned char *packet_buffer, unsigned int packet_length, 
						unsigned char **out_buffer)
    {
        struct udp_packet_keepalive_client * client = (struct udp_packet_keepalive_client *)packet_buffer;
        int id;
        unsigned char md5_value[MD5_LENGTH] = {0}, md5_out[16] = {0};
        char packet_type;
        unsigned int packet_id;

        packet_type = client->type;
        packet_id = client->packet_id;
        id = client->table_id;
        memcpy(md5_value, packet_buffer + sizeof(struct udp_packet_keepalive_client), MD5_LENGTH);

        // check md5

        /* check packet id */
        if (packet_id_ > 0 && packet_id == packet_id_ - 1)  //old packet
        {
            if (memcmp(last_md5_, md5_value, MD5_LENGTH))
            {
                cout << "[udp packet]Error resend packet md5 check. Discard it." << endl;
                return 0;
            }
            cout << "[udp thread]Resend packet" << endl;
            *out_buffer = reply_;
            return reply_length_;
        }
        else if (packet_id != packet_id_)
        {
            cout << "[udp packet]Packet id not equal to table. Discard it." << endl;
            return 0;
        }
		calculate_md5_with_secret(packet_buffer, sizeof(struct udp_packet_keepalive_client),
                                  secret_, UDP_PACKET_SECRET_KEY_LENGTH, md5_out);
		if (memcmp(md5_out, md5_value, MD5_LENGTH))
		{
			cout << "[udp packet]Error packet md5 check" << endl;
			return 0;
		}
        int reply_type = SERVER_QUERY;
        /* update traffic info */
        instant_up_rate_ = client->instant_up_rate;
        instant_down_rate_ = client->instant_down_rate;
        total_up_traffic_ = client->total_up_traffic;
        total_down_traffic_ = client->total_down_traffic;

        // status USER_KICKOFF_FIN\USER_EXIT is not affected by user packet
        switch (packet_type)
        {
        case CLIENT_QUERY:
            break;
        case CLIENT_KEEPALIVE:     //  Client keep alive packet
            if (status_ == USER_ACCOUNTING)
            {
                cout << "receive keep alive packet " << id_ << endl;
                if ((time(NULL) - update_time_) <= USER_UPDATE_MIN_TIME)
                {
                    cout << "[udp thread]User update too often, discard the packet." << cout;
                    return 0;
                }
                update();
                if (packet_id_ % RAD_KEEPALIVE_INTERVAL == 0) send_radius_account(rad_packet::ACCT_ALIVE);
                reply_type = SERVER_KEEPALIVE;
                //if (packet_id_ == 2) kick_off();
                break;
            }
            if (status_ == USER_KICKOFF)
            {
                goto kickoff;
            }
            break;
        case CLIENT_EXIT:          //client exit packet
            if (status_ == USER_ACCOUNTING)
            {
                cout << "accounting off " << id_ << endl;
                status_ = USER_EXIT;
                update();
                send_radius_account(rad_packet::ACCT_STOP);
                reply_type = SERVER_EXIT;
                break;
            }
kickoff:	if (status_ == USER_KICKOFF)
            {
                status_ = USER_KICKOFF_FIN;
                update();
                reply_type = SERVER_KICKOFF;
            }
            break;
        }
		if (send_message_.size()) send_message_ += '\0';             // mark the end of the message

        struct udp_packet_keepalive_server *server;     

        server = (struct udp_packet_keepalive_server *)reply_;
        server->type = reply_type;
        server->packet_id = packet_id_;
        /*  set upload download rate  */
        server->up_rate = limit_up_rate_;
        server->down_rate = limit_down_rate_;
        server->msg_length = send_message_.size();
        memcpy(reply_ + sizeof(struct udp_packet_keepalive_server),
			send_message_.data(), server->msg_length);        
		calculate_md5_with_secret(reply_, sizeof(struct udp_packet_keepalive_server) + server->msg_length,
                                  secret_, UDP_PACKET_SECRET_KEY_LENGTH, md5_out);
        memcpy((char *)server + sizeof(struct udp_packet_keepalive_server) + server->msg_length, md5_out, MD5_LENGTH);
        reply_length_ = sizeof(struct udp_packet_keepalive_server) + server->msg_length + MD5_LENGTH;
		                        
        memcpy(last_md5_, md5_value, MD5_LENGTH);   //keep the md5 for resend check
		send_message_.clear();

		*out_buffer = reply_;
        return reply_length_;
    }
    static void init()
    {
        for (int i = 1; i < MAX_USER_SERVER_ID; i++)
            free_id_.insert(i);
    }
    static int valid_accounting_id(user_server_id id)
    {
        if (online_user_.find(id) != online_user_.end())
            return 1;
        return 0;
    }
    static user_info_ptr get_user_info(user_server_id id)
    {
        if (online_user_.find(id) != online_user_.end())
            return online_user_[id];
        return user_info_ptr();
    }
    static user_info_ptr find_user_by_name(string name, char status = USER_ACCOUNTING)
    {
        user_info_ptr p;
        for (map<user_server_id, user_info_ptr>::iterator it = online_user_.begin(); it != online_user_.end(); it++)
        {
            if (it->second->get_name() == name && it->second->status_ == status)
                return it->second;
        }
        return p;
    }
	static void send_http_reply(server::connection_ptr &connection, string &reply, string length)
	{
        server::response_header headers[] =
        {
            {"Connection", "close"}
            , {"Content-Type", "text/plain"}
            , {"Server", "cpp-netlib/0.9"}
            , {"Content-Length", length.c_str()}
        };
		connection->set_status(server::connection::ok);
		connection->set_headers(boost::make_iterator_range(headers, headers+4));        
		connection->write(reply);
	}
	/*static string url_decode(string &url)
	{
		const char *src = url.data();
		size_t src_len = url.size();
		char dst[1024];
		size_t dst_len = sizeof(dst);
		size_t	i, j;
		int	a, b;
	#define	HEXTOI(x)	(isdigit(x) ? x - '0' : x - 'W')

		for (i = j = 0; i < src_len && j < dst_len - 1; i++, j++) {
			if (src[i] == '%' &&
				isxdigit(* (unsigned char *) (src + i + 1)) &&
				isxdigit(* (unsigned char *) (src + i + 2))) {
				a = tolower(* (unsigned char *) (src + i + 1));
				b = tolower(* (unsigned char *) (src + i + 2));
				dst[j] = ((HEXTOI(a) << 4) | HEXTOI(b)) & 0xff;
				i += 2;
			} else if (src[i] == '+') {
				dst[j] = ' ';
			} else {
				dst[j] = src[i];
			}
		}

		dst[j] = '\0';	
		string result(dst);
		return result;
	}*/
	static void admin_set_rate(server::connection_ptr &connection, key_value &pair)
	{		
        string reply;    
		string up, down;

		if (pair.find("username") == pair.end()) 
			reply = "failed";
		else if (pair.find("download") == pair.end()) 
			reply = "failed";
		else if (pair.find("upload") == pair.end()) 
			reply = "failed";
		else
		{
			up = pair["upload"];
			down = pair["download"];
			if (set_rate_by_name(pair["username"], boost::lexical_cast<unsigned int>(up), boost::lexical_cast<unsigned int>(down)) )
				reply = "okokok";
			else
				reply = "failed";
		}
		send_http_reply(connection, reply, boost::lexical_cast<string>(reply.size())); 
	}
	static void admin_send_message(server::connection_ptr &connection, key_value &pair)
	{		
        string reply;    
		string msg;

		if (pair.find("username") == pair.end()) 
			reply = "failed";
		else
		{
			if (pair.find("message") != pair.end())
				msg = pair["message"];
			if (send_message_by_name(pair["username"], msg))
				reply = "okokok";
			else
				reply = "failed";
		}
		send_http_reply(connection, reply, boost::lexical_cast<string>(reply.size())); 
	}
    static void admin_get_users(server::connection_ptr &connection)
    {
        string reply;        
		for (map<user_server_id, user_info_ptr>::iterator it = online_user_.begin(); it != online_user_.end(); it++ )
		{
			user_info_ptr &p = it->second;
			if (p->status_ == USER_ACCOUNTING)
        		reply += str( format("%s,%s,%ld,%d,%d,%d,%d;") % p->name_ % p->ip_.to_string() % p->login_time_
        			% p->instant_up_rate_ % p->instant_down_rate_ % p->total_up_traffic_ % p->total_down_traffic_);
		}
		if (reply == "") reply = "\"";
		send_http_reply(connection, reply, boost::lexical_cast<string>(reply.size())); 
    }
	static void admin_kick_users(server::connection_ptr &connection, key_value &pair)
	{
        string reply;    
		string msg;

		if (pair.find("username") == pair.end()) 
			reply = "failed";
		else
		{
			if (pair.find("message") != pair.end())
				msg = pair["message"];
			if (kick_user_by_name(pair["username"], msg))
				reply = "okokok";
			else
				reply = "failed";
		}
		send_http_reply(connection, reply, boost::lexical_cast<string>(reply.size())); 
	}
private:
    void check_deadline()
    {
        // delete this class if timeout
        if (output_deadline_.expires_at() <= deadline_timer::traits_type::now())
        {
            if (status_ == USER_ACCOUNTING) send_radius_account(rad_packet::ACCT_STOP);
            cout << "time out for user " << id_ << endl;
            lock lk(monitor);
            online_user_.erase(id_);
        }
        else
        {
            output_deadline_.async_wait(
                boost::bind(&user_info::check_deadline,
                            shared_from_this()));
        }
    }
    // user info
    int login_type_;
    string name_;
    string password_;
    int version_;
    boost::asio::ip::address ip_;
    bool is_ipv6_;
    unsigned int instant_up_rate_;
    unsigned int instant_down_rate_;
    unsigned int total_up_traffic_;
    unsigned int total_down_traffic_;


    // server info
    user_server_id id_;
    unsigned int packet_id_;
    unsigned char session_tx_[SESSION_TX_LENGTH + 1];
    unsigned char secret_[UDP_PACKET_SECRET_KEY_LENGTH + 1];
    unsigned char reply_[UDP_PACKET_MAX_LENGTH];
    unsigned int reply_length_;
    char status_;
    char last_md5_[MD5_LENGTH];
    time_t login_time_;
    time_t update_time_;
	string send_message_;

    // rad info
    unsigned int limit_up_rate_;
    unsigned int limit_down_rate_;
    int user_type_;
    int reply_code_;
    string reply_message_;

    // class var
    boost::mutex user_lock_;
    deadline_timer output_deadline_;



    static user_server_id alloc_id()
    {
        user_server_id id = 0;

        lock lk(monitor);
        if ( free_id_.empty()
                && !reserve_id_.empty() )
        {
            free_id_ = reserve_id_;
            reserve_id_.clear();
        }
        if (!free_id_.empty())
        {
            id = *free_id_.begin();
            free_id_.erase(id);
        }
        return id;
    }
    static void free_id(user_server_id id)
    {
        lock lk(monitor);
        if (id) reserve_id_.insert(id);
    }

    static boost::mutex monitor;
    static set<user_server_id> reserve_id_;
    static set<user_server_id> free_id_;
    static map<user_server_id, user_info_ptr> connecting_user_;
    static map<user_server_id, user_info_ptr> online_user_;

    static boost::asio::io_service& io_service_;
};




















#endif